/**
 * Razvan's public code. Copyright 2008 based on Apache license (share alike) see LICENSE.txt for
 * details.
 */
package com.razie.pub.http;

import java.io._
import java.net.ServerSocket;
import java.net.Socket;
import razie.base._
import scala.util.matching.Regex
import razie.SM
import com.razie.pub.comms._

object S {
   val logger = razie.Log
}

abstract class TelnetSM extends SM.StateMachine {
  import SM._
   
   val SB = 250
   val WILL = 251
   val WONT = 252
   val DO = 253
   val DONT = 254
   val IAC = 255
   val CR = IEvent('\r')
  
   val wwdd = Seq(WILL, WONT, DO, DONT)

   // negociated options, see http://support.microsoft.com/kb/231866
   val modes = scala.collection.mutable.HashMap (
         1 -> false, // echo - indicates single char echo mode...
         34 -> true  // linemode
         )
   def mode (f:Boolean) (sm:StateMachine, t:Transition, e:Event) {
     val i = e.asInstanceOf[IEvent].i
     modes.put (i,f)
     logger trace ("TELNET option "+i+" turned "+(if(f) "ON" else "OFF"))
  }

  
   // based on diagram at http://tomi.vanek.sk/index.php?page=telnet
   
   implicit val sm = this
   
   val sstates @ (data, cmd, app, param, neg, subneg) = ("data", "cmd", "app", "param", "neg", "subneg")
   override def start = state("data")
   
   override val transitions : Seq[Transition] = 
         (data, IAC)             -> cmd ::
         (data, 0)               -> data :: // NOP - don't know why i get these after CR
         (data, 10)              -> data :: // NOP - LF ignored?
         (data, 13)              -> data + eatLine + echo ("") :: // CR
         (data, {_:Event=>true}) -> data + eatChar + echo ("") :: // remaining chars
         (cmd, IAC)              -> data ::
         (cmd, Seq(WILL, WONT, DO, DONT)) -> neg + push ::
         // TODO 3-2 should negociate stuff, i.e. reply with will/won't
         (neg, {_:Event=>last==SM(DO)})   -> data + mode(true) + pop :: 
         (neg, {_:Event=>last==SM(DONT)}) -> data + mode(false) + pop ::
         (neg, AnyEvent) -> data + echo("interesting sequence...") + pop ::  // What is this?
         (cmd, SB) -> subneg :: 
         (""".*""".r, CR) -> data :: // it's important to reset the thing on ENTRE
          Nil

    def eatChar (sm:StateMachine, t:Transition, e:Event) 
    def eatLine (sm:StateMachine, t:Transition, e:Event) 
}

class MyTelnetSM (val p:Puffer, val session:SessionControl, val socket:MyServerSocket, val cs:ContentServer) 
extends TelnetSM {
   import SM._

   val useMultiple = true
   
   def shouldEcho = modes(1)//.getOrElse(true)
   var selection = razie.Listi[(Char, ActionItem)] ()
   
   override def eatChar (sm:StateMachine, t:Transition, e:Event) {
      val c = e.asInstanceOf[IEvent].i.toChar
      if (c == '\t') {
         cassist (c)
      } else if (selection.size > 0) {
         selection.filter (_._1 == c) headOption match {
            case Some((_,x)) => {
               p eat x.name
               session print x.name
               selection = razie.Listi[(Char, ActionItem)] ()
            }
            case None => {
               session println ""
               session println "GRESHKA - try again :)"
               selection foreach (t => session println "      " + t._1 + ") " + t._2.label)
               session print p.acc
            }
         }
      } else {
         p eat c
         if (shouldEcho) session print c.toString
      }
   }
   
   def cassist (c:Char) : Boolean = {
      S.logger trace "char: " + c + "   code: " + c.toInt
      if (c == '\t') {
         val opt = cs.options(p.acc, session.sessionId)
         if (opt.size == 1) {
            val x = opt.head
            S.logger log "option: " + x; 
            session print x.name
            p eat x.name
         } else if (opt.size > 1 && useMultiple) {
            var i = 0
            session println ""
            session println "--> Selection: "
            opt foreach { x => selection.append ((('1'+i).toChar, x)); i+=1 }
            selection foreach (t => session println "      " + t._1 + ") " + t._2.label)
            session print p.acc
         }
         true
      } 
     false
   }
   
   override def eatLine (sm:StateMachine, t:Transition, e:Event) {
      p.lineReady
      p.b = ! eatLine (p.line)
      p.clear
   }
   
   def eatLine (line:String) = {
      S.logger trace "line: " + line
      session print "\r\n"
      
      if (line == "exit" || line == "quit") {
         session print "stopping..\r\n"
         session.stop  // done
      } else {
         val parms=  new java.util.Properties()
         parms.put ("sessionId", session.sessionId)
         val reply = cs.exec (p.line, "telnet", parms, socket, razie.AA())
         if (reply != null)
            session print reply.toString
         session print "\r\n"
         true
      }
      false
   }
}

/** all we need to callback to control the session */
trait SessionControl {
   def print (s:String)
   def println (s:String)
   def stop
   def sessionId : String
}

/** the state (buffer) ? */
case class Puffer {
   var acc = ""
   var line = ""
   
   var b=true
   var ignoreCnt=0
   
   def eat (c:Char) = acc = acc+c
   def eat (s:String) = acc = acc+s
   def lineReady {line=acc}
   def ignore (i:Int) = ignoreCnt = i
   def clear {acc=""; line=""; b=true; ignoreCnt=0}
}

class TelnetReceiver (val socket:MyServerSocket, cs:ContentServer) extends SocketReceiver with SessionControl with Runnable {
//   val in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   val in = socket.getInputStream();
   val out = new PrintStream(socket.getOutputStream());
   
   override def print   (s:String) = out print s
   override def println (s:String) = out print s + "\r\n"
   override def stop = keepReading = false
   val sessionId : String = cs.mkSession()

   var keepReading = true
   
   val sm = new MyTelnetSM (new Puffer(), this, socket, cs)
  
   override def run () {
      S.logger log "receiving"
   while (keepReading) { // one loop per "session

      // echo mode - 
//      if (! sm.modes.getOrElse(1, false)) {
         while (keepReading) { // one loop per character
            val b = in.read
            if (b == -1)
               keepReading = false
            else
               sm move SM(b)
         }
   }
   
   socket.close();
   }
}

